﻿using SharpDX;
using SharpDX.Direct3D11;
using SharpDX.DXGI;
using System;
using System.Runtime.InteropServices;

namespace Engine.Graphics.TextureLoader
{
    public class DDSHelper
    {
        #region Private Fields

        private static readonly LegacyMap[] LegacyMaps = new[]
                                                             {
                                                                 new LegacyMap(Format.BC1_UNorm, ConversionFlags.None, DDS.PixelFormat.DXT1), // D3DFMT_DXT1
                                                                 new LegacyMap(Format.BC2_UNorm, ConversionFlags.None, DDS.PixelFormat.DXT3), // D3DFMT_DXT3
                                                                 new LegacyMap(Format.BC3_UNorm, ConversionFlags.None, DDS.PixelFormat.DXT5), // D3DFMT_DXT5

                                                                 new LegacyMap(Format.BC2_UNorm, ConversionFlags.None, DDS.PixelFormat.DXT2), // D3DFMT_DXT2 (ignore premultiply)
                                                                 new LegacyMap(Format.BC3_UNorm, ConversionFlags.None, DDS.PixelFormat.DXT4), // D3DFMT_DXT4 (ignore premultiply)

                                                                 new LegacyMap(Format.BC4_UNorm, ConversionFlags.None, DDS.PixelFormat.BC4_UNorm),
                                                                 new LegacyMap(Format.BC4_SNorm, ConversionFlags.None, DDS.PixelFormat.BC4_SNorm),
                                                                 new LegacyMap(Format.BC5_UNorm, ConversionFlags.None, DDS.PixelFormat.BC5_UNorm),
                                                                 new LegacyMap(Format.BC5_SNorm, ConversionFlags.None, DDS.PixelFormat.BC5_SNorm),

                                                                 new LegacyMap(Format.BC4_UNorm, ConversionFlags.None, new DDS.PixelFormat(DDS.PixelFormatFlags.FourCC, new SharpDX.Multimedia.FourCC('A', 'T', 'I', '1'), 0, 0, 0, 0, 0)),
                                                                 new LegacyMap(Format.BC5_UNorm, ConversionFlags.None, new DDS.PixelFormat(DDS.PixelFormatFlags.FourCC, new SharpDX.Multimedia.FourCC('A', 'T', 'I', '2'), 0, 0, 0, 0, 0)),

                                                                 new LegacyMap(Format.R8G8_B8G8_UNorm, ConversionFlags.None, DDS.PixelFormat.R8G8_B8G8), // D3DFMT_R8G8_B8G8
                                                                 new LegacyMap(Format.G8R8_G8B8_UNorm, ConversionFlags.None, DDS.PixelFormat.G8R8_G8B8), // D3DFMT_G8R8_G8B8

                                                                 new LegacyMap(Format.B8G8R8A8_UNorm, ConversionFlags.None, DDS.PixelFormat.A8R8G8B8), // D3DFMT_A8R8G8B8 (uses DXGI 1.1 format)
                                                                 new LegacyMap(Format.B8G8R8X8_UNorm, ConversionFlags.None, DDS.PixelFormat.X8R8G8B8), // D3DFMT_X8R8G8B8 (uses DXGI 1.1 format)
                                                                 new LegacyMap(Format.R8G8B8A8_UNorm, ConversionFlags.None, DDS.PixelFormat.A8B8G8R8), // D3DFMT_A8B8G8R8
                                                                 new LegacyMap(Format.R8G8B8A8_UNorm, ConversionFlags.NoAlpha, DDS.PixelFormat.X8B8G8R8), // D3DFMT_X8B8G8R8
                                                                 new LegacyMap(Format.R16G16_UNorm, ConversionFlags.None, DDS.PixelFormat.G16R16), // D3DFMT_G16R16

                                                                 new LegacyMap(Format.R10G10B10A2_UNorm, ConversionFlags.Swizzle, new DDS.PixelFormat(DDS.PixelFormatFlags.Rgb, 0, 32, 0x000003ff, 0x000ffc00, 0x3ff00000, 0xc0000000)),
                                                                 // D3DFMT_A2R10G10B10 (D3DX reversal issue workaround)
                                                                 new LegacyMap(Format.R10G10B10A2_UNorm, ConversionFlags.None, new DDS.PixelFormat(DDS.PixelFormatFlags.Rgb, 0, 32, 0x3ff00000, 0x000ffc00, 0x000003ff, 0xc0000000)),
                                                                 // D3DFMT_A2B10G10R10 (D3DX reversal issue workaround)

                                                                 new LegacyMap(Format.R8G8B8A8_UNorm, ConversionFlags.Expand
                                                                                                           | ConversionFlags.NoAlpha
                                                                                                           | ConversionFlags.Format888, DDS.PixelFormat.R8G8B8), // D3DFMT_R8G8B8

                                                                 new LegacyMap(Format.B5G6R5_UNorm, ConversionFlags.Format565, DDS.PixelFormat.R5G6B5), // D3DFMT_R5G6B5
                                                                 new LegacyMap(Format.B5G5R5A1_UNorm, ConversionFlags.Format5551, DDS.PixelFormat.A1R5G5B5), // D3DFMT_A1R5G5B5
                                                                 new LegacyMap(Format.B5G5R5A1_UNorm, ConversionFlags.Format5551
                                                                                                           | ConversionFlags.NoAlpha, new DDS.PixelFormat(DDS.PixelFormatFlags.Rgb, 0, 16, 0x7c00, 0x03e0, 0x001f, 0x0000)), // D3DFMT_X1R5G5B5

                                                                 new LegacyMap(Format.R8G8B8A8_UNorm, ConversionFlags.Expand
                                                                                                           | ConversionFlags.Format8332, new DDS.PixelFormat(DDS.PixelFormatFlags.Rgb, 0, 16, 0x00e0, 0x001c, 0x0003, 0xff00)),
                                                                 // D3DFMT_A8R3G3B2
                                                                 new LegacyMap(Format.B5G6R5_UNorm, ConversionFlags.Expand
                                                                                                         | ConversionFlags.Format332, new DDS.PixelFormat(DDS.PixelFormatFlags.Rgb, 0, 8, 0xe0, 0x1c, 0x03, 0x00)), // D3DFMT_R3G3B2

                                                                 new LegacyMap(Format.R8_UNorm, ConversionFlags.None, DDS.PixelFormat.L8), // D3DFMT_L8
                                                                 new LegacyMap(Format.R16_UNorm, ConversionFlags.None, DDS.PixelFormat.L16), // D3DFMT_L16
                                                                 new LegacyMap(Format.R8G8_UNorm, ConversionFlags.None, DDS.PixelFormat.A8L8), // D3DFMT_A8L8

                                                                 new LegacyMap(Format.A8_UNorm, ConversionFlags.None, DDS.PixelFormat.A8), // D3DFMT_A8

                                                                 new LegacyMap(Format.R16G16B16A16_UNorm, ConversionFlags.None, new DDS.PixelFormat(DDS.PixelFormatFlags.FourCC, 36, 0, 0, 0, 0, 0)), // D3DFMT_A16B16G16R16
                                                                 new LegacyMap(Format.R16G16B16A16_SNorm, ConversionFlags.None, new DDS.PixelFormat(DDS.PixelFormatFlags.FourCC, 110, 0, 0, 0, 0, 0)), // D3DFMT_Q16W16V16U16
                                                                 new LegacyMap(Format.R16_Float, ConversionFlags.None, new DDS.PixelFormat(DDS.PixelFormatFlags.FourCC, 111, 0, 0, 0, 0, 0)), // D3DFMT_R16F
                                                                 new LegacyMap(Format.R16G16_Float, ConversionFlags.None, new DDS.PixelFormat(DDS.PixelFormatFlags.FourCC, 112, 0, 0, 0, 0, 0)), // D3DFMT_G16R16F
                                                                 new LegacyMap(Format.R16G16B16A16_Float, ConversionFlags.None, new DDS.PixelFormat(DDS.PixelFormatFlags.FourCC, 113, 0, 0, 0, 0, 0)), // D3DFMT_A16B16G16R16F
                                                                 new LegacyMap(Format.R32_Float, ConversionFlags.None, new DDS.PixelFormat(DDS.PixelFormatFlags.FourCC, 114, 0, 0, 0, 0, 0)), // D3DFMT_R32F
                                                                 new LegacyMap(Format.R32G32_Float, ConversionFlags.None, new DDS.PixelFormat(DDS.PixelFormatFlags.FourCC, 115, 0, 0, 0, 0, 0)), // D3DFMT_G32R32F
                                                                 new LegacyMap(Format.R32G32B32A32_Float, ConversionFlags.None, new DDS.PixelFormat(DDS.PixelFormatFlags.FourCC, 116, 0, 0, 0, 0, 0)), // D3DFMT_A32B32G32R32F

                                                                 new LegacyMap(Format.R32_Float, ConversionFlags.None, new DDS.PixelFormat(DDS.PixelFormatFlags.Rgb, 0, 32, 0xffffffff, 0x00000000, 0x00000000, 0x00000000)),
                                                                 // D3DFMT_R32F (D3DX uses FourCC 114 instead)

                                                                 new LegacyMap(Format.R8G8B8A8_UNorm, ConversionFlags.Expand
                                                                                                           | ConversionFlags.Pal8
                                                                                                           | ConversionFlags.FormatA8P8, new DDS.PixelFormat(DDS.PixelFormatFlags.Pal8, 0, 16, 0, 0, 0, 0)), // D3DFMT_A8P8
                                                                 new LegacyMap(Format.R8G8B8A8_UNorm, ConversionFlags.Expand
                                                                                                           | ConversionFlags.Pal8, new DDS.PixelFormat(DDS.PixelFormatFlags.Pal8, 0, 8, 0, 0, 0, 0)), // D3DFMT_P8
                                                                 new LegacyMap( Format.B4G4R4A4_UNorm,     ConversionFlags.Format4444,        DDS.PixelFormat.A4R4G4B4 ), // D3DFMT_A4R4G4B4 (uses DXGI 1.2 format)
                                                                 new LegacyMap( Format.B4G4R4A4_UNorm,     ConversionFlags.NoAlpha
                                                                                                  | ConversionFlags.Format4444,      new DDS.PixelFormat(DDS.PixelFormatFlags.Rgb,       0, 16, 0x0f00,     0x00f0,     0x000f,     0x0000     ) ), // D3DFMT_X4R4G4B4 (uses DXGI 1.2 format)
                                                                 new LegacyMap( Format.B4G4R4A4_UNorm,     ConversionFlags.Expand
                                                                                                  | ConversionFlags.Format44,        new DDS.PixelFormat(DDS.PixelFormatFlags.Luminance, 0,  8, 0x0f,       0x00,       0x00,       0xf0       ) ), // D3DFMT_A4L4 (uses DXGI 1.2 format)
                                                             };

        #endregion Private Fields

        #region Public Enums

        [Flags]
        public enum ConversionFlags
        {
            None = 0x0,
            Expand = 0x1, // Conversion requires expanded pixel size
            NoAlpha = 0x2, // Conversion requires setting alpha to known value
            Swizzle = 0x4, // BGR/RGB order swizzling required
            Pal8 = 0x8, // Has an 8-bit palette
            Format888 = 0x10, // Source is an 8:8:8 (24bpp) format
            Format565 = 0x20, // Source is a 5:6:5 (16bpp) format
            Format5551 = 0x40, // Source is a 5:5:5:1 (16bpp) format
            Format4444 = 0x80, // Source is a 4:4:4:4 (16bpp) format
            Format44 = 0x100, // Source is a 4:4 (8bpp) format
            Format332 = 0x200, // Source is a 3:3:2 (8bpp) format
            Format8332 = 0x400, // Source is a 8:3:3:2 (16bpp) format
            FormatA8P8 = 0x800, // Has an 8-bit palette with an alpha channel
            CopyMemory = 0x1000, // The content of the memory passed to the DDS Loader is copied to another internal buffer.
            DX10 = 0x10000, // Has the 'DX10' extension header
        };

        #endregion Public Enums

        #region Public Methods

        /// <summary>
        /// Simple DDS loader ported from http://msdn.microsoft.com/en-us/library/windows/apps/jj651550.aspx
        /// </summary>
        /// <param name="d3dDevice"></param>
        /// <param name="ddsData"></param>
        /// <param name="ddsDataSize"></param>
        /// <param name="texture"></param>
        /// <param name="textureView"></param>
        // THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
        // ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
        // THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
        // PARTICULAR PURPOSE.
        //
        // Copyright (c) Microsoft Corporation. All rights reserved.
        public static unsafe void CreateDDSTextureFromMemory(
            SharpDX.Direct3D11.Device d3dDevice,
            IntPtr ddsData,
            int ddsDataSize,
            out SharpDX.Direct3D11.Resource texture,
            out ShaderResourceView textureView
            )
        {
            if (d3dDevice == null || ddsData == IntPtr.Zero)
            {
                throw new ArgumentException();
            }

            // Validate DDS file in memory

            ImageDescription imageDesc;
            ConversionFlags convFlags;
            if (!DecodeDDSHeader(ddsData, ddsDataSize, DDSFlags.None, out imageDesc, out convFlags))
                throw new Exception("Invalid DDS header");

            int offset = sizeof(uint) + Utilities.SizeOf<DDS.Header>();
            if ((convFlags & ConversionFlags.DX10) != 0)
                offset += Utilities.SizeOf<DDS.HeaderDXT10>();

            var pal8 = (int*)0;
            if ((convFlags & ConversionFlags.Pal8) != 0)
            {
                pal8 = (int*)((byte*)(ddsData) + offset);
                offset += (256 * sizeof(uint));
            }

            if (ddsDataSize < offset)
                throw new InvalidOperationException();

            //ptrdiff_t offset = sizeof(uint32) + sizeof(DDS_HEADER) + (bDXT10Header ? sizeof(DDS_HEADER_DXT10) : 0);
            if (imageDesc.MipLevels > 1)
            { }
            CreateTextureFromDDS(d3dDevice, imageDesc, IntPtr.Add(ddsData, offset), ddsDataSize - offset, out texture, out textureView);
        }

        #endregion Public Methods

        #region Private Methods

        // Simple DDS loader ported from http://msdn.microsoft.com/en-us/library/windows/apps/jj651550.aspx
        private static void CreateD3DResources(
            SharpDX.Direct3D11.Device d3dDevice,
            TextureDimension resDim,
            int width,
            int height,
            int depth,
            int mipCount,
            int arraySize,
            Format format,
            bool isCubeMap,
            DataBox[] initData,
            //_In_reads_(mipCount*arraySize) D3D11_SUBRESOURCE_DATA* initData,
            out SharpDX.Direct3D11.Resource texture,
            out SharpDX.Direct3D11.ShaderResourceView textureView
            //_Out_opt_ ID3D11Resource** texture,
            //_Out_opt_ ID3D11ShaderResourceView** textureView
            )
        {
            texture = null;
            textureView = null;

            if (d3dDevice == null || initData == null)
            {
                return;
            }

            switch (resDim)
            {
                case TextureDimension.Texture1D:// D3D11_RESOURCE_DIMENSION_TEXTURE1D:
                    {
                        Texture1DDescription desc = new Texture1DDescription();
                        //D3D11_TEXTURE1D_DESC desc;
                        desc.Width = width;
                        desc.MipLevels = mipCount;
                        desc.ArraySize = arraySize;
                        desc.Format = format;
                        desc.Usage = ResourceUsage.Default;
                        desc.BindFlags = BindFlags.ShaderResource;// D3D11_BIND_SHADER_RESOURCE;
                        desc.CpuAccessFlags = CpuAccessFlags.None;
                        desc.OptionFlags = ResourceOptionFlags.None;

                        Texture1D tex = null;
                        //ID3D11Texture1D* tex = nullptr;
                        tex = new Texture1D(d3dDevice, desc, initData);
                        //hr = d3dDevice->CreateTexture1D(&desc, initData, &tex);

                        if (tex != null)
                        {
                            ShaderResourceViewDescription SRVDesc = new ShaderResourceViewDescription();
                            //D3D11_SHADER_RESOURCE_VIEW_DESC SRVDesc;
                            //memset(&SRVDesc, 0, sizeof(SRVDesc));
                            SRVDesc.Format = format;

                            if (arraySize > 1)
                            {
                                SRVDesc.Dimension = SharpDX.Direct3D.ShaderResourceViewDimension.Texture1DArray;// D3D_SRV_DIMENSION_TEXTURE1DARRAY;
                                SRVDesc.Texture1DArray.MipLevels = desc.MipLevels;
                                SRVDesc.Texture1DArray.ArraySize = arraySize;
                            }
                            else
                            {
                                SRVDesc.Dimension = SharpDX.Direct3D.ShaderResourceViewDimension.Texture1D;// D3D_SRV_DIMENSION_TEXTURE1D;
                                SRVDesc.Texture1D.MipLevels = desc.MipLevels;
                            }

                            textureView = new ShaderResourceView(d3dDevice, tex, SRVDesc);
                            //hr = d3dDevice->CreateShaderResourceView(tex, &SRVDesc, textureView);

                            if (textureView == null)
                            {
                                tex.Dispose();
                                return;
                            }

                            texture = tex;
                        }
                    }
                    break;

                case TextureDimension.TextureCube:
                case TextureDimension.Texture2D:// D3D11_RESOURCE_DIMENSION_TEXTURE2D:
                    {
                        Texture2DDescription desc = new Texture2DDescription();
                        desc.Width = width;
                        desc.Height = height;
                        desc.MipLevels = mipCount;
                        desc.ArraySize = arraySize;
                        desc.Format = format;
                        desc.SampleDescription.Count = 1;
                        desc.SampleDescription.Quality = 0;
                        desc.Usage = ResourceUsage.Default;
                        desc.BindFlags = BindFlags.ShaderResource;
                        desc.CpuAccessFlags = CpuAccessFlags.None;
                        desc.OptionFlags = (isCubeMap) ? ResourceOptionFlags.TextureCube : ResourceOptionFlags.None;

                        Texture2D tex = null;
                        tex = new Texture2D(d3dDevice, desc, initData);
                        tex.DebugName = "Test";
                        //hr = d3dDevice->CreateTexture2D(&desc, initData, &tex);

                        if (tex != null)
                        {
                            ShaderResourceViewDescription SRVDesc = new ShaderResourceViewDescription();
                            SRVDesc.Format = format;

                            if (isCubeMap)
                            {
                                if (arraySize > 6)
                                {
                                    SRVDesc.Dimension = SharpDX.Direct3D.ShaderResourceViewDimension.TextureCubeArray;
                                    SRVDesc.TextureCubeArray.MipLevels = desc.MipLevels;

                                    // Earlier we set arraySize to (NumCubes * 6)
                                    SRVDesc.TextureCubeArray.CubeCount = arraySize / 6;
                                }
                                else
                                {
                                    SRVDesc.Dimension = SharpDX.Direct3D.ShaderResourceViewDimension.TextureCube;
                                    SRVDesc.TextureCube.MipLevels = desc.MipLevels;
                                }
                            }
                            else if (arraySize > 1)
                            {
                                SRVDesc.Dimension = SharpDX.Direct3D.ShaderResourceViewDimension.Texture2DArray;
                                SRVDesc.Texture2DArray.MipLevels = desc.MipLevels;
                                SRVDesc.Texture2DArray.ArraySize = arraySize;
                            }
                            else
                            {
                                SRVDesc.Dimension = SharpDX.Direct3D.ShaderResourceViewDimension.Texture2D;
                                SRVDesc.Texture2D.MipLevels = desc.MipLevels;
                            }

                            textureView = new ShaderResourceView(d3dDevice, tex, SRVDesc);
                            //hr = d3dDevice->CreateShaderResourceView(tex, &SRVDesc, textureView);

                            texture = tex;
                        }
                    }
                    break;

                case TextureDimension.Texture3D:
                    {
                        Texture3DDescription desc = new Texture3DDescription();
                        desc.Width = width;
                        desc.Height = height;
                        desc.Depth = depth;
                        desc.MipLevels = mipCount;
                        desc.Format = format;
                        desc.Usage = ResourceUsage.Default;
                        desc.BindFlags = BindFlags.ShaderResource;
                        desc.CpuAccessFlags = CpuAccessFlags.None;
                        desc.OptionFlags = ResourceOptionFlags.None;

                        Texture3D tex = null;
                        tex = new Texture3D(d3dDevice, desc, initData);
                        //hr = d3dDevice->CreateTexture3D(&desc, initData, &tex);

                        if (tex != null)
                        {
                            ShaderResourceViewDescription SRVDesc = new ShaderResourceViewDescription();
                            SRVDesc.Format = format;
                            SRVDesc.Dimension = SharpDX.Direct3D.ShaderResourceViewDimension.Texture3D;
                            SRVDesc.Texture3D.MipLevels = desc.MipLevels;

                            textureView = new ShaderResourceView(d3dDevice, tex, SRVDesc);
                            texture = tex;
                        }
                    }
                    break;
            }
        }

        // Simple DDS loader ported from http://msdn.microsoft.com/en-us/library/windows/apps/jj651550.aspx
        private static void CreateTextureFromDDS(
            SharpDX.Direct3D11.Device d3dDevice,
            ImageDescription imageDesc,
            //SharpDX.Toolkit.Graphics.DDS.Header header,
            //DDS_HEADER* header,
            IntPtr bitData,
            //_In_reads_bytes_(bitSize) const byte* bitData,
            int bitSize,
            out SharpDX.Direct3D11.Resource texture,
            //_Out_opt_ ID3D11Resource** texture,
            out ShaderResourceView textureView
            //_Out_opt_ ID3D11ShaderResourceView** textureView,
            )
        {
            int width = imageDesc.Width;
            int height = imageDesc.Height;
            int depth = imageDesc.Depth;

            int arraySize = imageDesc.ArraySize;
            Format format = imageDesc.Format;
            bool isCubeMap = imageDesc.Dimension == TextureDimension.TextureCube;

            int mipCount = imageDesc.MipLevels;// MipMapCount;
            if (0 == mipCount)
            {
                mipCount = 1;
            }

            // Create the texture
            DataBox[] initData = new DataBox[mipCount * arraySize];
            //std::unique_ptr<D3D11_SUBRESOURCE_DATA> initData(new D3D11_SUBRESOURCE_DATA[mipCount * arraySize]);

            int maxsize = 1;
            if (isCubeMap)
            {
                maxsize = SharpDX.Direct3D11.Resource.MaximumTextureCubeSize;
            }
            else
            {
                maxsize = (imageDesc.Dimension == TextureDimension.Texture3D)
                    ? SharpDX.Direct3D11.Resource.MaximumTexture3DSize
                    : SharpDX.Direct3D11.Resource.MaximumTexture2DSize;
            }

            int skipMip = 0;
            int twidth = 0;
            int theight = 0;
            int tdepth = 0;
            FillInitData(width, height, depth, mipCount, arraySize, format, maxsize, bitSize, bitData, out twidth, out theight, out tdepth, out skipMip, initData);

            CreateD3DResources(d3dDevice, imageDesc.Dimension, twidth, theight, tdepth, mipCount - skipMip, arraySize, format, isCubeMap, initData, out texture, out textureView);
        }

        /// <summary>
        /// Decodes DDS header including optional DX10 extended header
        /// </summary>
        /// <param name="headerPtr">Pointer to the DDS header.</param>
        /// <param name="size">Size of the DDS content.</param>
        /// <param name="flags">Flags used for decoding the DDS header.</param>
        /// <param name="description">Output texture description.</param>
        /// <param name="convFlags">Output conversion flags.</param>
        /// <exception cref="ArgumentException">If the argument headerPtr is null</exception>
        /// <exception cref="InvalidOperationException">If the DDS header contains invalid datas.</exception>
        /// <returns>True if the decoding is successfull, false if this is not a DDS header.</returns>
        private static unsafe bool DecodeDDSHeader(IntPtr headerPtr, int size, DDSFlags flags, out ImageDescription description, out ConversionFlags convFlags)
        {
            description = new ImageDescription();
            convFlags = ConversionFlags.None;

            if (headerPtr == IntPtr.Zero)
                throw new ArgumentException("Pointer to DDS header cannot be null", "headerPtr");

            if (size < (Utilities.SizeOf<DDS.Header>() + sizeof(uint)))
                return false;

            // DDS files always start with the same magic number ("DDS ")
            if (*(uint*)(headerPtr) != DDS.MagicHeader)
                return false;

            var header = *(DDS.Header*)((byte*)headerPtr + sizeof(int));

            // Verify header to validate DDS file
            if (header.Size != Utilities.SizeOf<DDS.Header>() || header.PixelFormat.Size != Utilities.SizeOf<DDS.PixelFormat>())
                return false;

            // Setup MipLevels
            description.MipLevels = header.MipMapCount;
            if (description.MipLevels == 0)
                description.MipLevels = 1;

            // Check for DX10 extension
            if ((header.PixelFormat.Flags & DDS.PixelFormatFlags.FourCC) != 0 && (new SharpDX.Multimedia.FourCC('D', 'X', '1', '0') == header.PixelFormat.FourCC))
            {
                // Buffer must be big enough for both headers and magic value
                if (size < (Utilities.SizeOf<DDS.Header>() + sizeof(uint) + Utilities.SizeOf<DDS.HeaderDXT10>()))
                    return false;

                var headerDX10 = *(DDS.HeaderDXT10*)((byte*)headerPtr + sizeof(int) + Utilities.SizeOf<DDS.Header>());
                convFlags |= ConversionFlags.DX10;

                description.ArraySize = headerDX10.ArraySize;
                if (description.ArraySize == 0)
                    throw new InvalidOperationException("Unexpected ArraySize == 0 from DDS HeaderDX10 ");

                description.Format = headerDX10.DXGIFormat;
                if (!FormatHelper.IsValid(description.Format))
                    throw new InvalidOperationException("Invalid Format from DDS HeaderDX10 ");

                switch (headerDX10.ResourceDimension)
                {
                    case ResourceDimension.Texture1D:

                        // D3DX writes 1D textures with a fixed Height of 1
                        if ((header.Flags & DDS.HeaderFlags.Height) != 0 && header.Height != 1)
                            throw new InvalidOperationException("Unexpected Height != 1 from DDS HeaderDX10 ");

                        description.Width = header.Width;
                        description.Height = 1;
                        description.Depth = 1;
                        description.Dimension = TextureDimension.Texture1D;
                        break;

                    case ResourceDimension.Texture2D:
                        if ((headerDX10.MiscFlags & ResourceOptionFlags.TextureCube) != 0)
                        {
                            description.ArraySize *= 6;
                            description.Dimension = TextureDimension.TextureCube;
                        }
                        else
                        {
                            description.Dimension = TextureDimension.Texture2D;
                        }

                        description.Width = header.Width;
                        description.Height = header.Height;
                        description.Depth = 1;
                        break;

                    case ResourceDimension.Texture3D:
                        if ((header.Flags & DDS.HeaderFlags.Volume) == 0)
                            throw new InvalidOperationException("Texture3D missing HeaderFlags.Volume from DDS HeaderDX10");

                        if (description.ArraySize > 1)
                            throw new InvalidOperationException("Unexpected ArraySize > 1 for Texture3D from DDS HeaderDX10");

                        description.Width = header.Width;
                        description.Height = header.Height;
                        description.Depth = header.Depth;
                        description.Dimension = TextureDimension.Texture3D;
                        break;

                    default:
                        throw new InvalidOperationException(string.Format("Unexpected dimension [{0}] from DDS HeaderDX10", headerDX10.ResourceDimension));
                }
            }
            else
            {
                description.ArraySize = 1;

                if ((header.Flags & DDS.HeaderFlags.Volume) != 0)
                {
                    description.Width = header.Width;
                    description.Height = header.Height;
                    description.Depth = header.Depth;
                    description.Dimension = TextureDimension.Texture3D;
                }
                else
                {
                    if ((header.CubemapFlags & DDS.CubemapFlags.CubeMap) != 0)
                    {
                        // We require all six faces to be defined
                        if ((header.CubemapFlags & DDS.CubemapFlags.AllFaces) != DDS.CubemapFlags.AllFaces)
                            throw new InvalidOperationException("Unexpected CubeMap, expecting all faces from DDS Header");

                        description.ArraySize = 6;
                        description.Dimension = TextureDimension.TextureCube;
                    }
                    else
                    {
                        description.Dimension = TextureDimension.Texture2D;
                    }

                    description.Width = header.Width;
                    description.Height = header.Height;
                    description.Depth = 1;
                    // Note there's no way for a legacy Direct3D 9 DDS to express a '1D' texture
                }

                description.Format = GetDXGIFormat(ref header.PixelFormat, flags, out convFlags);

                if (description.Format == Format.Unknown)
                    throw new InvalidOperationException("Unsupported PixelFormat from DDS Header");
            }

            // Special flag for handling BGR DXGI 1.1 formats
            if ((flags & DDSFlags.ForceRgb) != 0)
            {
                switch ((Format)description.Format)
                {
                    case Format.B8G8R8A8_UNorm:
                        description.Format = Format.R8G8B8A8_UNorm;
                        convFlags |= ConversionFlags.Swizzle;
                        break;

                    case Format.B8G8R8X8_UNorm:
                        description.Format = Format.R8G8B8A8_UNorm;
                        convFlags |= ConversionFlags.Swizzle | ConversionFlags.NoAlpha;
                        break;

                    case Format.B8G8R8A8_Typeless:
                        description.Format = Format.R8G8B8A8_Typeless;
                        convFlags |= ConversionFlags.Swizzle;
                        break;

                    case Format.B8G8R8A8_UNorm_SRgb:
                        description.Format = Format.R8G8B8A8_UNorm_SRgb;
                        convFlags |= ConversionFlags.Swizzle;
                        break;

                    case Format.B8G8R8X8_Typeless:
                        description.Format = Format.R8G8B8A8_Typeless;
                        convFlags |= ConversionFlags.Swizzle | ConversionFlags.NoAlpha;
                        break;

                    case Format.B8G8R8X8_UNorm_SRgb:
                        description.Format = Format.R8G8B8A8_UNorm_SRgb;
                        convFlags |= ConversionFlags.Swizzle | ConversionFlags.NoAlpha;
                        break;
                }
            }

            // Pass DDSFlags copy memory to the conversion flags
            if ((flags & DDSFlags.CopyMemory) != 0)
                convFlags |= ConversionFlags.CopyMemory;

            // Special flag for handling 16bpp formats
            if ((flags & DDSFlags.No16Bpp) != 0)
            {
                switch ((Format)description.Format)
                {
                    case Format.B5G6R5_UNorm:
                    case Format.B5G5R5A1_UNorm:
                    case Format.B4G4R4A4_UNorm:
                        description.Format = Format.R8G8B8A8_UNorm;
                        convFlags |= ConversionFlags.Expand;
                        if (description.Format == Format.B5G6R5_UNorm)
                            convFlags |= ConversionFlags.NoAlpha;
                        break;
                }
            }
            return true;
        }

        // Simple DDS loader ported from http://msdn.microsoft.com/en-us/library/windows/apps/jj651550.aspx
        private static void FillInitData(
            int width,
            int height,
            int depth,
            int mipCount,
            int arraySize,
            Format format,
            int maxsize,
            int bitSize,
            IntPtr bitData,
            out int twidth,
            out int theight,
            out int tdepth,
            out int skipMip,
            DataBox[] initData
            )
        {
            if (bitData == IntPtr.Zero)
            {
                throw new ArgumentNullException("bitData");
            }

            skipMip = 0;
            twidth = 0;
            theight = 0;
            tdepth = 0;

            int numBytes = 0;
            int rowBytes = 0;
            int numRows = 0;
            IntPtr pSrcBits = bitData;
            IntPtr pEndBits = IntPtr.Add(bitData, bitSize);

            int index = 0;
            for (int j = 0; j < arraySize; j++)
            {
                int w = width;
                int h = height;
                int d = depth;
                for (int i = 0; i < mipCount; i++)
                {
                    //FormatHelper.
                    if (FormatHelper.IsCompressed(format))
                    {
                        int numBlocksWide = 0;
                        if (width > 0)
                        {
                            numBlocksWide = Math.Max(1, (w + 3) / 4);
                        }
                        int numBlocksHigh = 0;
                        if (height > 0)
                        {
                            numBlocksHigh = Math.Max(1, (h + 3) / 4);
                        }
                        int bytesPerBlock = 0;

                        switch (format)
                        {
                            case Format.BC1_Typeless:
                            case Format.BC1_UNorm:
                            case Format.BC1_UNorm_SRgb:
                            case Format.BC4_Typeless:
                            case Format.BC4_UNorm:
                            case Format.BC4_SNorm:
                                bytesPerBlock = 8;
                                break;

                            case Format.BC2_Typeless:
                            case Format.BC2_UNorm:
                            case Format.BC2_UNorm_SRgb:
                            case Format.BC3_Typeless:
                            case Format.BC3_UNorm:
                            case Format.BC3_UNorm_SRgb:
                            case Format.BC5_Typeless:
                            case Format.BC5_UNorm:
                            case Format.BC5_SNorm:
                            case Format.BC6H_Typeless:
                            case Format.BC6H_Uf16:
                            case Format.BC6H_Sf16:
                            case Format.BC7_Typeless:
                            case Format.BC7_UNorm:
                            case Format.BC7_UNorm_SRgb:
                                bytesPerBlock = 16;
                                break;

                            default:
                                break;
                        }
                        rowBytes = numBlocksWide * bytesPerBlock;
                        numRows = numBlocksHigh;
                    }
                    else if (FormatHelper.IsPacked(format))
                    {
                        rowBytes = ((width + 1) >> 1) * 4;
                        numRows = height;
                    }
                    else
                    {
                        int bpp = FormatHelper.SizeOfInBits(format);// BitsPerPixel(fmt);
                        rowBytes = (width * bpp + 7) / 8; // round up to nearest byte
                        numRows = height;
                    }

                    numBytes = rowBytes * numRows;
                    //GetSurfaceInfo(w, h, format, NumBytes, RowBytes, NumRows);

                    if ((mipCount <= 1) || maxsize <= 0 || (w <= maxsize && h <= maxsize && d <= maxsize))
                    {
                        if (twidth <= 0)
                        {
                            twidth = w;
                            theight = h;
                            tdepth = d;
                        }

                        initData[index].DataPointer = pSrcBits;
                        initData[index].RowPitch = rowBytes;
                        initData[index].SlicePitch = numBytes;
                        ++index;
                    }
                    else
                    {
                        ++skipMip;
                    }

                    if (pSrcBits.ToInt64() + (numBytes * d) > pEndBits.ToInt64())
                    {
                        throw new IndexOutOfRangeException("Gone past end of DDS");
                    }

                    pSrcBits = IntPtr.Add(pSrcBits, numBytes * d);

                    w = w >> 1;
                    h = h >> 1;
                    d = d >> 1;
                    if (w == 0)
                    {
                        w = 1;
                    }
                    if (h == 0)
                    {
                        h = 1;
                    }
                    if (d == 0)
                    {
                        d = 1;
                    }
                }
            }

            if (index == 0)
            {
                throw new Exception("Failed to create DataBoxes");
            }
        }

        // We do not support the following legacy Direct3D 9 formats:
        //      BumpDuDv D3DFMT_V8U8, D3DFMT_Q8W8V8U8, D3DFMT_V16U16, D3DFMT_A2W10V10U10
        //      BumpLuminance D3DFMT_L6V5U5, D3DFMT_X8L8V8U8
        //      FourCC "UYVY" D3DFMT_UYVY
        //      FourCC "YUY2" D3DFMT_YUY2
        //      FourCC 117 D3DFMT_CxV8U8
        //      ZBuffer D3DFMT_D16_LOCKABLE
        //      FourCC 82 D3DFMT_D32F_LOCKABLE
        private static Format GetDXGIFormat(ref DDS.PixelFormat pixelFormat, DDSFlags flags, out ConversionFlags conversionFlags)
        {
            conversionFlags = ConversionFlags.None;

            int index = 0;
            for (index = 0; index < LegacyMaps.Length; ++index)
            {
                var entry = LegacyMaps[index];

                if ((pixelFormat.Flags & entry.PixelFormat.Flags) != 0)
                {
                    if ((entry.PixelFormat.Flags & DDS.PixelFormatFlags.FourCC) != 0)
                    {
                        if (pixelFormat.FourCC == entry.PixelFormat.FourCC)
                            break;
                    }
                    else if ((entry.PixelFormat.Flags & DDS.PixelFormatFlags.Pal8) != 0)
                    {
                        if (pixelFormat.RGBBitCount == entry.PixelFormat.RGBBitCount)
                            break;
                    }
                    else if (pixelFormat.RGBBitCount == entry.PixelFormat.RGBBitCount)
                    {
                        // RGB, RGBA, ALPHA, LUMINANCE
                        if (pixelFormat.RBitMask == entry.PixelFormat.RBitMask
                            && pixelFormat.GBitMask == entry.PixelFormat.GBitMask
                            && pixelFormat.BBitMask == entry.PixelFormat.BBitMask
                            && pixelFormat.ABitMask == entry.PixelFormat.ABitMask)
                            break;
                    }
                }
            }

            if (index >= LegacyMaps.Length)
                return Format.Unknown;

            conversionFlags = LegacyMaps[index].ConversionFlags;
            var format = LegacyMaps[index].Format;

            if ((conversionFlags & ConversionFlags.Expand) != 0 && (flags & DDSFlags.NoLegacyExpansion) != 0)
                return Format.Unknown;

            if ((format == Format.R10G10B10A2_UNorm) && (flags & DDSFlags.NoR10B10G10A2Fixup) != 0)
            {
                conversionFlags ^= ConversionFlags.Swizzle;
            }

            return format;
        }

        #endregion Private Methods

        #region Public Structs

        // Note that many common DDS reader/writers (including D3DX) swap the
        // the RED/BLUE masks for 10:10:10:2 formats. We assumme
        // below that the 'backwards' header mask is being used since it is most
        // likely written by D3DX. The more robust solution is to use the 'DX10'
        // header extension and specify the Format.R10G10B10A2_UNorm format directly
        /// <summary>
        /// A description for <see cref="Image"/>.
        /// </summary>
        [StructLayout(LayoutKind.Sequential)]
        public struct ImageDescription : IEquatable<ImageDescription>
        {
            /// <summary>
            /// The dimension of a texture.
            /// </summary>
            public TextureDimension Dimension;

            /// <summary>
            /// <dd> <p>Texture width (in texels). The  range is from 1 to <see cref="SharpDX.Direct3D11.Resource.MaximumTexture1DSize"/> (16384). However, the range is actually constrained by the feature level at which you create the rendering device. For more information about restrictions, see Remarks.</p> </dd>
            /// </summary>
            /// <remarks>
            /// This field is valid for all textures: <see cref="Texture1D"/>, <see cref="Texture2D"/>, <see cref="Texture3D"/> and <see cref="TextureCube"/>.
            /// </remarks>
            /// <msdn-id>ff476252</msdn-id>
            /// <unmanaged>unsigned int Width</unmanaged>
            /// <unmanaged-short>unsigned int Width</unmanaged-short>
            public int Width;

            /// <summary>
            /// <dd> <p>Texture height (in texels). The  range is from 1 to <see cref="SharpDX.Direct3D11.Resource.MaximumTexture3DSize"/> (2048). However, the range is actually constrained by the feature level at which you create the rendering device. For more information about restrictions, see Remarks.</p> </dd>
            /// </summary>
            /// <remarks>
            /// This field is only valid for <see cref="Texture2D"/>, <see cref="Texture3D"/> and <see cref="TextureCube"/>.
            /// </remarks>
            /// <msdn-id>ff476254</msdn-id>
            /// <unmanaged>unsigned int Height</unmanaged>
            /// <unmanaged-short>unsigned int Height</unmanaged-short>
            public int Height;

            /// <summary>
            /// <dd> <p>Texture depth (in texels). The  range is from 1 to <see cref="SharpDX.Direct3D11.Resource.MaximumTexture3DSize"/> (2048). However, the range is actually constrained by the feature level at which you create the rendering device. For more information about restrictions, see Remarks.</p> </dd>
            /// </summary>
            /// <remarks>
            /// This field is only valid for <see cref="Texture3D"/>.
            /// </remarks>
            /// <msdn-id>ff476254</msdn-id>
            /// <unmanaged>unsigned int Depth</unmanaged>
            /// <unmanaged-short>unsigned int Depth</unmanaged-short>
            public int Depth;

            /// <summary>
            /// <dd> <p>Number of textures in the array. The  range is from 1 to <see cref="SharpDX.Direct3D11.Resource.MaximumTexture1DArraySize"/> (2048). However, the range is actually constrained by the feature level at which you create the rendering device. For more information about restrictions, see Remarks.</p> </dd>
            /// </summary>
            /// <remarks>
            /// This field is only valid for <see cref="Texture1D"/>, <see cref="Texture2D"/> and <see cref="TextureCube"/>
            /// </remarks>
            /// <remarks>
            /// This field is only valid for textures: <see cref="Texture1D"/>, <see cref="Texture2D"/> and <see cref="TextureCube"/>.
            /// </remarks>
            /// <msdn-id>ff476252</msdn-id>
            /// <unmanaged>unsigned int ArraySize</unmanaged>
            /// <unmanaged-short>unsigned int ArraySize</unmanaged-short>
            public int ArraySize;

            /// <summary>
            /// <dd> <p>The maximum number of mipmap levels in the texture. See the remarks in <strong><see cref="SharpDX.Direct3D11.ShaderResourceViewDescription.Texture1DResource"/></strong>. Use 1 for a multisampled texture; or 0 to generate a full set of subtextures.</p> </dd>
            /// </summary>
            /// <msdn-id>ff476252</msdn-id>
            /// <unmanaged>unsigned int MipLevels</unmanaged>
            /// <unmanaged-short>unsigned int MipLevels</unmanaged-short>
            public int MipLevels;

            /// <summary>
            /// <dd> <p>Texture format (see <strong><see cref="SharpDX.DXGI.Format"/></strong>).</p> </dd>
            /// </summary>
            /// <msdn-id>ff476252</msdn-id>
            /// <unmanaged>DXGI_FORMAT Format</unmanaged>
            /// <unmanaged-short>DXGI_FORMAT Format</unmanaged-short>
            public Format Format;

            public bool Equals(ImageDescription other)
            {
                return Dimension.Equals(other.Dimension) && Width == other.Width && Height == other.Height && Depth == other.Depth && ArraySize == other.ArraySize && MipLevels == other.MipLevels && Format.Equals(other.Format);
            }

            public override bool Equals(object obj)
            {
                if (ReferenceEquals(null, obj)) return false;
                return obj is ImageDescription && Equals((ImageDescription)obj);
            }

            public override int GetHashCode()
            {
                unchecked
                {
                    int hashCode = Dimension.GetHashCode();
                    hashCode = (hashCode * 397) ^ Width;
                    hashCode = (hashCode * 397) ^ Height;
                    hashCode = (hashCode * 397) ^ Depth;
                    hashCode = (hashCode * 397) ^ ArraySize;
                    hashCode = (hashCode * 397) ^ MipLevels;
                    hashCode = (hashCode * 397) ^ Format.GetHashCode();
                    return hashCode;
                }
            }

            public static bool operator ==(ImageDescription left, ImageDescription right)
            {
                return left.Equals(right);
            }

            public static bool operator !=(ImageDescription left, ImageDescription right)
            {
                return !left.Equals(right);
            }

            public override string ToString()
            {
                return string.Format("Dimension: {0}, Width: {1}, Height: {2}, Depth: {3}, Format: {4}, ArraySize: {5}, MipLevels: {6}", Dimension, Width, Height, Depth, Format, ArraySize, MipLevels);
            }
        }

        [StructLayout(LayoutKind.Sequential, Pack = 1)]
        public struct LegacyMap
        {
            /// <summary>
            /// Initializes a new instance of the <see cref="LegacyMap" /> struct.
            /// </summary>
            /// <param name="format">The format.</param>
            /// <param name="conversionFlags">The conversion flags.</param>
            /// <param name="pixelFormat">The pixel format.</param>
            public LegacyMap(Format format, ConversionFlags conversionFlags, DDS.PixelFormat pixelFormat)
            {
                Format = format;
                ConversionFlags = conversionFlags;
                PixelFormat = pixelFormat;
            }

            public Format Format;
            public ConversionFlags ConversionFlags;
            public DDS.PixelFormat PixelFormat;
        };

        #endregion Public Structs
    }
}